/*
 * init_pilua.c
 *
 * Plugin interface to the Lua Programming Language
 *
 * (c) 2024, Lau
 *
 */

#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <stdio.h>
#include <glib.h>

#include "wmem_strbuf.h"
#include "wmem_strutils.h"

#include "init_pilua.h"
#include "list.h"
#include "pi_filesystem.h"

#include "tvbuff.h"
#include "packet.h"
#include "ftypes.h"
#include "proto.h"
#include "value_string.h"
#include "true_false_strings.h"
#include "unit_strings.h"

#include "array.h"
#include "pilua.h"

#include "funnel.h"

#define plugins_dir     "./plugins/LuaScripts"
#define DIR_SEPARATOR   "/"

/* linked list of Lua plugins */
typedef struct _pilua_plugin {
    char       *name;            /**< plugin name */
    char       *version;         /**< plugin version */
    char       *filename;        /**< plugin filename */
    struct _pilua_plugin *next;
} pilua_plugin;

static pilua_plugin *pilua_plugin_list = NULL;

static lua_State* L;

static void (*pilua_gui_print_func_ptr)(const char *, void *);
static void *pilua_gui_print_data_ptr;
static int pilua_lua_print_func_ref = LUA_NOREF;

/* XXX: global variables? Really?? Yuck. These could be done differently,
   using the Lua registry */
tvbuff_t* lua_tvb;
packet_info* lua_pinfo;
struct _pilua_treeitem* lua_tree;

int lua_dissectors_table_ref = LUA_NOREF;

static int proto_lua;

static int hf_pilua_fake;
static int hf_pilua_text;

//static expert_field ei_lua_error;

static int ett_pilua_traceback;

//static bool lua_pinfo_end(wmem_allocator_t *allocator, wmem_cb_event_t event, void *user_data)
//{
//    clear_outstanding_Tvb();
//    clear_outstanding_TvbRange();
//    clear_outstanding_Pinfo();
//    clear_outstanding_Column();
//    clear_outstanding_Columns();
//    clear_outstanding_PrivateTable();
//    clear_outstanding_TreeItem();
//    clear_outstanding_FieldInfo();
//    clear_outstanding_FuncSavers();

    /* keep invoking this callback later? */
//    return false;
//}

//static int pilua_not_register_menu(lua_State* LS)
//{
//    luaL_error(LS, "too late to register a menu");
//    return 0;
//}

/* a getter for pilua_tree.c's TreeItem_add_item_any() to use */
int get_hf_pilua_text(void)
{
    return hf_pilua_text;
}

// Attach the lua traceback to the proto_tree
static int dissector_error_handler(lua_State *LS)
{
    // Entering, stack: [ error_handler, dissector, errmsg ]
// TODO: show lua exception error
    proto_item *tb_item;
    proto_tree *tb_tree;

    // Add the expert info Lua error message
//    proto_tree_add_expert_format(lua_tree->tree, lua_pinfo, &ei_lua_error, lua_tvb, 0, 0,
//            "Lua Error: %s", lua_tostring(LS, -1));

    // Create a new proto sub_tree for the traceback
    tb_item = proto_tree_add_text_internal(lua_tree->tree, lua_tvb, 0, 0, "Lua Traceback");
    tb_tree = proto_item_add_subtree(tb_item, ett_pilua_traceback);

    // Push the traceback onto the stack
    // After call, stack: [ error_handler, dissector, errmsg, tb_string ]
    luaL_traceback(LS, LS, NULL, 1);

    // Get the string length of the traceback. Note that the string
    // has a terminating NUL, but string_length doesn't include it.
    // The lua docs say the string can have NULs in it too, but we
    // ignore that because the traceback string shouldn't have them.
    // This function does not own the string; it's still owned by lua.
    size_t string_length;
    const char *orig_tb_string = lua_tolstring(LS, -1, &string_length);

    // We make the copy so we can modify the string. Don't forget the
    // extra byte for the terminating NUL!
    char *tb_string = (char*)malloc(string_length+1);
    memcpy(tb_string, orig_tb_string, string_length+1);

    // The string has tabs and new lines in it
    // We will add proto_items for each new-line-delimited sub-string.
    // We also convert tabs to spaces, because the Wireshark GUI
    // shows tabs literally as "\t".

    // 'beginning' is the beginning of the sub-string
    char *beginning = tb_string;

    // 'p' is the pointer to the byte as we iterate over the string
    char *p = tb_string;

    size_t i;
    bool skip_initial_tabs = true;
    size_t last_eol_i = 0;
    for (i = 0; i < string_length; i++) {
        // At the beginning of a sub-string, we will convert tabs to spaces
        if (skip_initial_tabs) {
            if (*p == '\t') {
                *p = ' ';
            } else {
                // Once we hit the first non-tab character in a substring,
                // we won't convert tabs (until the next substring)
                skip_initial_tabs = false;
            }
        }
        // If we see a newline, we add the substring to the proto tree
        if (*p == '\n') {
            // Terminate the string.
            *p = '\0';
            proto_tree_add_text_internal(tb_tree, lua_tvb, 0, 0, "%s", beginning);
            beginning = ++p;
            skip_initial_tabs = true;
            last_eol_i = i;
        } else {
            ++p;
        }
    }

    // The last portion of the string doesn't have a newline, so add it here
    // after the loop. But to be sure, check that we didn't just add it, in
    // case lua decides to change it in the future.
    if ( last_eol_i < i-1 ) {
        proto_tree_add_text_internal(tb_tree, lua_tvb, 0, 0, "%s", beginning);
    }

    // Cleanup
    free(tb_string);

    // Return the same original error message
    return -2;
}

int dissect_lua(tvbuff_t* tvb, packet_info *pinfo, proto_tree* tree, void* data)
{printf("dissect_lua\n");fflush(stdout);
    int consumed_bytes = tvb_captured_length(tvb);
    tvbuff_t *saved_lua_tvb = lua_tvb;
    packet_info *saved_lua_pinfo = lua_pinfo;
    struct _pilua_treeitem *saved_lua_tree = lua_tree;

    lua_pinfo = pinfo;
    lua_tvb = tvb;

    /*
     * almost equivalent to Lua:
     * dissectors[current_proto](tvb,pinfo,tree)
     */

    // set the stack top be index 0
    lua_settop(L, 0);

    // After call, stack: [ error_handler_func ]
    lua_pushcfunction(L, dissector_error_handler);

    // Push the dissectors table onto the the stack
    // After call, stack: [ error_handler_func, dissectors_table ]
    lua_rawgeti(L, LUA_REGISTRYINDEX, lua_dissectors_table_ref);

    // Push a copy of the current_proto string onto the stack
    // After call, stack: [ error_handler_func, dissectors_table, current_proto ]
    lua_pushstring(L, pinfo->proto_name);

    // dissectors_table[current_proto], a dissector, goes into the stack
    // The key (current_proto) is popped off the stack.
    // After call, stack: [ error_handler_func, dissectors_table, dissector ]
    lua_gettable(L, -2);

    // We don't need the dissectors_table in the stack
    // After call, stack: [ error_handler_func, dissector ]
    lua_remove(L, 2);

    // Is the dissector a function?
    if (lua_isfunction(L, 2)) {

        // After call, stack: [ error_handler_func, dissector, tvb ]
        push_Tvb(L, tvb);
        // After call, stack: [ error_handler_func, dissector, tvb, pinfo ]
        push_Pinfo(L, pinfo);
        // After call, stack: [ error_handler_func, dissector, tvb, pinfo, TreeItem ]
        lua_tree = push_TreeItem(L, tree, proto_tree_add_item(tree, hf_pilua_fake, tvb, 0, 0, ENC_NA));
        proto_item_set_hidden(lua_tree->item);
printf("call dissector function\n");fflush(stdout);
        if  ( lua_pcall(L, /*num_args=*/3, /*num_results=*/1, /*error_handler_func_stack_position=*/1) ) {
            // do nothing; the traceback error message handler function does everything
            printf("call dissector function error!!!\n");fflush(stdout);
        } else {
            /* if the Lua dissector reported the consumed bytes, pass it to our caller */
            if (lua_isnumber(L, -1)) {
                /* we got the consumed bytes or the missing bytes as a negative number */
                consumed_bytes = pilua_toint(L, -1);
                lua_pop(L, 1);
            }
        }

    } else {
        printf("call dissector function error\n");fflush(stdout);
        // TODO: show lua error on ui
//        proto_tree_add_expert_format(tree, pinfo, &ei_lua_error, tvb, 0, 0,
//                    "Lua Error: did not find the %s dissector in the dissectors table", pinfo->current_proto);
    }

//    wmem_register_callback(pinfo->pool, lua_pinfo_end, NULL);

    lua_pinfo = saved_lua_pinfo;
    lua_tree = saved_lua_tree;
    lua_tvb = saved_lua_tvb;
printf("dissect_lua end\n");fflush(stdout);
    return consumed_bytes;
}

static void iter_table_and_call(lua_State* LS, const char* table_name, lua_CFunction error_handler)
{
    lua_settop(LS, 0);

    lua_pushcfunction(LS, error_handler);
    lua_getglobal(LS, table_name);

    if (!lua_istable(LS, 2)) {
        printf("Lua: either `%s' does not exist or it is not a table!\n", table_name);
        lua_close(LS);
        L = NULL;
        return;
    }

    lua_pushnil(LS);

    while (lua_next(LS, 2)) {
        const char* name = lua_tostring(L, -2);

        if (lua_isfunction(LS, -1)) {
            if ( lua_pcall(LS, 0, 0, 1) ) {
                lua_pop(LS, 1);
            }
        } else {
            printf("Lua: Something not a function got its way into the %s.%s", table_name, name);
            lua_close(LS);
            L = NULL;
            return;
        }
    }

    lua_settop(LS, 0);
}

//static int init_error_handler(lua_State* LS)
//{
//    const char* error =  lua_tostring(LS, 1);
//    printf("Lua: Error during execution of initialization:\n %s", error);
//    return 0;
//}

//static bool init_routine_initialized;
//static void pilua_init_routine(void)
//{
//    if ( ! init_routine_initialized ) {
//        /*
//         * This must be done only once during the entire life of
//         * tshark/wireshark, because it must be done only once per the life of
//         * the Lua state/engine, so we guard this with the boolean above;
//         * otherwise it would occur every time a file is opened (every time
//         * epan_new() is called).
//         *
//         * If we ever allow the Lua state to be restarted, or to have multiple
//         * Lua states, we'll need to change this.
//         */
////        lua_prime_all_fields(NULL);
//        init_routine_initialized = true;
//    }

//    if (L) {
//        iter_table_and_call(L, PILUA_INIT_ROUTINES, init_error_handler);
//    }

//}

//static void pilua_cleanup_routine(void)
//{
//    if (L) {
//        iter_table_and_call(L, PILUA_INIT_ROUTINES, init_error_handler);
//    }
//}

static int prefs_changed_error_handler(lua_State* LS)
{
    const char* error = lua_tostring(LS,1);
    printf("Lua: Error during execution of prefs apply callback:\n %s", error);
    return 0;
}

void pilua_prefs_changed(void)
{
    if (L) {
        iter_table_and_call(L, PILUA_PREFS_CHANGED, prefs_changed_error_handler);
    }
}

static const char *getF(lua_State *LS, void *ud, size_t *size)
{
    FILE *f = (FILE *)ud;
    static char buff[512];

    if (feof(f)) return NULL;
    *size = fread(buff, 1, sizeof(buff), f);

    return (*size > 0) ? buff : NULL;
}

static int error_handler_with_callback(lua_State *LS)
{
    const char *msg = lua_tostring(LS, 1);
    luaL_traceback(LS, LS, msg, 1);     /* push message with traceback.  */
    lua_remove(LS, -2);                 /* remove original msg */
    return 1;
}

static void pilua_add_plugin(const char *name, const char *version, const char *filename)
{
    pilua_plugin *new_plug, *lua_plug;

    lua_plug = pilua_plugin_list;
    new_plug = malloc(sizeof(pilua_plugin));

    if (!lua_plug) { /* the list is empty */
        pilua_plugin_list = new_plug;
    } else {
        while (lua_plug->next != NULL) {
            lua_plug = lua_plug->next;
        }
        lua_plug->next = new_plug;
    }

    new_plug->name = strdup(name);
    new_plug->version = strdup(version);
    new_plug->filename = strdup(filename);
    new_plug->next = NULL;
}

static void pilua_clear_plugin_list(void)
{
    pilua_plugin *lua_plug;

    while (pilua_plugin_list) {
        lua_plug = pilua_plugin_list;
        pilua_plugin_list = pilua_plugin_list->next;
        free(lua_plug->name);
        free(lua_plug->version);
        free(lua_plug->filename);
        free(lua_plug);
    }
}

static int lua_script_push_args(const int script_num)
{
    char* argname;
    const char* argvalue = NULL;
    int i, count = 0;

    argname = malloc(64);
    snprintf(argname, 64, "lua_script%d", script_num);

//    count = ex_opt_count(argname);

//    for (i = 0; i < count; i++) {
//        argvalue = ex_opt_get_nth(argname, i);
//        lua_pushstring(L, argvalue);
//    }

    free(argname);
    return count;
}

/* Prepends a path to the global package path (used by require)
 * We could add a custom loader to package.searchers instead, which
 * might be more useful in some way. We could also have some method of
 * saving and restoring the path, instead of constantly adding to it. */
static void prepend_path(const char* dirname)
{
    const char* path;

    /* prepend the directory name to _G.package.path */
    lua_getglobal(L, "package"); /* get the package table from the global table */
    lua_getfield(L, -1, "path");    /* get the path field from the package table */
    path = luaL_checkstring(L, -1); /* get the path string */
    lua_pop(L, 1);                  /* pop the path string */
    /* prepend the path */
    /* We could also add "?/init.lua" for packages. */
    lua_pushfstring(L, "%s" DIR_SEPARATOR "?.lua;%s",
                    dirname, path);
    lua_setfield(L, -2, "path");
    lua_pop(L, 1);
}

#define FILE_NAME_KEY "__FILE__"
#define DIR_NAME_KEY "__DIR__"
#define DIR_SEP_NAME_KEY "__DIR_SEPARATOR__"
/* assumes a loaded chunk's function is on top of stack */
static void set_file_environment(const char* filename, const char* dirname)
{
    lua_newtable(L); /* environment for script (index 3) */

    /* XXX - Do we need this? A script can get the filename and path from
     * debug.getinfo/lua_Debug source */
    lua_pushstring(L, filename); /* tell the script about its filename */
    lua_setfield(L, -2, FILE_NAME_KEY); /* make it accessible at __FILE__ */

    lua_pushstring(L, dirname); /* tell the script about its dirname */
    lua_setfield(L, -2, DIR_NAME_KEY); /* make it accessible at __DIR__ */

    /* XXX - Do we need this? package.config:sub(1,1) is the directory separator */
    lua_pushstring(L, DIR_SEPARATOR); /* tell the script the directory separator */
    lua_setfield(L, -2, DIR_SEP_NAME_KEY); /* make it accessible at __DIR_SEPARATOR__ */

    lua_newtable(L); /* new metatable */

    lua_pushglobaltable(L);

    lua_setfield(L, -2, "__index"); /* make metatable's __index point to global table */

    lua_setmetatable(L, -2); /* pop metatable, set it as metatable of environment */

    lua_setupvalue(L, -2, 1); /* pop environment and assign it to upvalue 1 */

    /* The result of this is a new environment that falls back to reading from
     * the global environment for any entry not in the new environment, but
     * can't change the global environment while running.
     *
     * Unfortunately, while we could create a file-local package.path, it
     * wouldn't have any effect on the path actually used by requires, since
     * the requires function has as an upvalue the original package table;
     * we can't change the behavior of requires without changing it for
     * the global environment and for all scripts.
     *
     * For some of the same reasons, if the script itself calls require(),
     * that actually affects the original global environment too, so this
     * sandboxing doesn't completely work to keep the global environment
     * unchanged. Is it worth it?
     */
}

/* This loads plugins like Lua modules / libraries, using require().
 * It has the advantage that loaded files get add to the package.loaded
 * table, so files aren't run again by require() either.
 */
static bool lua_load_plugin(const char* filename)
{
    int status;
    char *trimmed;

    const char *basename = get_basename(filename);
    char *dot = strrchr(basename, '.');
    if (dot) {
        trimmed = strdup(basename);
        trimmed[dot - basename] = '\0';
    } else {
        trimmed = strdup(basename);
    }

    lua_settop(L, 0);

    lua_pushcfunction(L, error_handler_with_callback);

    lua_getglobal(L, "require");

    lua_pushstring(L, trimmed);
    free(trimmed);

    /* Ignore the return from "require" by passing 0 as nresults to lua_pcall
     * (we could add it to the global table using the name as dolibrary() does,
     * but that is deprecated style in modern Lua.)
     */
    status = lua_pcall(L, 1, 0, 1);
    if (status != LUA_OK) {
        switch (status) {
            case LUA_ERRRUN:
                printf("Lua: Error during loading: %s\n", lua_tostring(L, -1));
                break;
            case LUA_ERRMEM:
                printf("Lua: Error during loading: out of memory");
                break;
            case LUA_ERRERR:
                printf("Lua: Error during loading: error while retrieving error message");
                break;
            default:
                printf("Lua: Error during loading: unknown error %d", status);
                break;
        }
    }

    return status == LUA_OK;
}

/* If file_count > 0 then it's a command-line-added user script, and the count
 * represents which user script it is (first=1, second=2, etc.).
 * If dirname != NULL, then it's a user script and the dirname will get put in a file environment
 * If dirname == NULL then it's a wireshark script and no file environment is created
 */
static bool lua_load_script(const char* filename, const char* dirname, const int file_count)
{
    FILE* file;
    int error;
    int numargs = 0;

    if (! ( file = fopen(filename,"r")) ) {
//        report_open_failure(filename, errno, false);
        return false;
    }

    lua_settop(L,0);

    lua_pushcfunction(L, error_handler_with_callback);
    /* The source argument should start with '@' to indicate a file. */
    lua_pushfstring(L, "@%s", filename);

    error = lua_load(L, getF, file, lua_tostring(L, -1), NULL);

    switch (error) {
        case 0: /* LUA_OK */
            if (dirname) {
                set_file_environment(filename, dirname);
            }
            if (file_count > 0) {
                numargs = lua_script_push_args(file_count);
            }
            error = lua_pcall(L, numargs, 0, 1);
            if (error) {
                switch (error) {
                    case LUA_ERRRUN:
                        printf("Lua: Error during loading:\n%s", lua_tostring(L, -1));
                        break;
                    case LUA_ERRMEM:
                        printf("Lua: Error during loading: out of memory");
                        break;
                    case LUA_ERRERR:
                        printf("Lua: Error during loading: error while retrieving error message");
                        break;
                    default:
                        printf("Lua: Error during loading: unknown error %d", error);
                        break;
                }
            }
            break;

        case LUA_ERRSYNTAX:
            printf("Lua: syntax error: %s", lua_tostring(L, -1));
            break;

        case LUA_ERRMEM:
            printf("Lua: memory allocation error during precompilation of %s", filename);
            break;

        default:
            printf("Lua: unknown error during precompilation of %s: %d", filename, error);
            break;
    }
    fclose(file);
    lua_pop(L, 2);  /* pop the filename and error handler */
    return error == 0;
}

/* This one is used to load the init.lua scripts, or anything else
 * that shouldn't really be considered a real plugin.
 */
static bool lua_load_internal_script(const char* filename) {
    return lua_load_script(filename, NULL, 0);
}

/* This one is used to load plugins from the plugin directories,
 *   or scripts from the command line. The latter have file_count > 0.
 */
static bool lua_load_plugin_script(const char* name,
                                   const char* filename,
                                   const char* dirname,
                                   const int file_count)
{
    if (file_count > 0 ? lua_load_script(filename, dirname, file_count) :
                         lua_load_plugin(filename)) {
        pilua_add_plugin(name, get_current_plugin_version(), filename);
        clear_current_plugin_version();
        return true;
    }
    return false;
}

static int pilua_panic(lua_State* LS)
{
    printf("LUA PANIC: %s", lua_tostring(LS, -1));
    abort();
    return 0; /* keep gcc happy */
}

struct file_node {
    list_t list;
    char filename[PATH_MAX];/*文件名*/
}__attribute__((packed));

static int lua_load_plugins(const char *dirname, void *client_data,
                            bool count_only, const bool is_user,
                            int depth)
{
    DIR           *dir;                 /* scanned directory */
    struct dirent *dir_info;            /* current file */
    char          *filename, *dot;
    const char    *name;
    char           full_path[PATH_MAX];
    struct stat    fstat;
    struct file_node *file;
    int            plugins_counter = 0;
    list_t  dirnames;
    list_t  filenames;
    list_t *pos, *tmp;

    INIT_LIST_HEAD(&dirnames);
    INIT_LIST_HEAD(&filenames);

    if ((dir = opendir(dirname)) == NULL) {
        return 0;
    }
    while ((dir_info = readdir(dir)) != NULL) {
        name = dir_info->d_name;

        if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) {
            /* skip "." and ".." */
            continue;
        }
        if (depth == 0 && strcmp(name, "init.lua") == 0) {
            /* If we are in the root directory skip the special "init.lua"
             * file that was already loaded before every other user script.
             * (If we are below the root script directory we just treat it like any other
             * lua script.) */
            continue;
        }

        // Get the absolute Path
        strcpy(full_path, dirname);
        strcat(full_path, DIR_SEPARATOR);
        strcat(full_path, name);

        stat(full_path, &fstat);
        if (S_ISDIR(fstat.st_mode)) {
            file = (struct file_node *)malloc(sizeof(struct file_node));
            strcpy(file->filename, full_path);
            list_add_tail(&file->list, &dirnames);
            continue;
        }

        /* skip files starting with . */
        if (name[0] == '.') {
            continue;
        }

        /* skip anything but files with .lua suffix */
        dot = strrchr(name, '.');
        if (dot == NULL || strcasecmp(dot+1, "lua") != 0) {
            continue;
        }

        if (S_ISREG(fstat.st_mode)) {
            file = (struct file_node *)malloc(sizeof(struct file_node));
            strcpy(file->filename, full_path);
            list_add_tail(&file->list, &filenames);
        }
    }
    closedir(dir);

    /* Depth first; ie, process subdirectories (in ASCIIbetical order) before files */
    if (list_empty(&dirnames) != true) {
        list_for_each_safe(pos, tmp, &dirnames){
            file = list_entry(pos, struct file_node, list);
            plugins_counter += lua_load_plugins((const char *)file->filename, client_data, count_only, is_user, depth + 1);
        }
        list_for_each_safe(pos, tmp, &dirnames){
            file = list_entry(pos, struct file_node, list);
            /*删除节点*/
            list_del(&file->list);
            free(file);
        }
    }

    /* Process files in ASCIIbetical order */
    if (list_empty(&filenames) != 1) {
        /* If this is not the root of the plugin directory, add it to the path.
         * XXX - Should we remove it after we're done with this directory? */
        if (depth > 0) {
            prepend_path(dirname);
        }
        list_for_each_safe(pos, tmp, &filenames){
            file = list_entry(pos, struct file_node, list);
            filename = (char *)file->filename;
            name = strrchr(filename, '/') + 1;

            if (!count_only) {
                lua_load_plugin_script(name, filename, is_user ? dirname : NULL, 0);
            }
            plugins_counter++;
        }
        list_for_each_safe(pos, tmp, &filenames){
            file = list_entry(pos, struct file_node, list);
            /*删除节点*/
            list_del(&file->list);
            free(file);
        }
    }

    return plugins_counter;
}

static int lua_load_global_plugins(void *client_data, bool count_only)
{
    return lua_load_plugins(get_plugins_dir(), client_data, count_only, false, 0);
}

int pilua_count_plugins(void)
{
    int plugins_counter;

    /* count global scripts */
    plugins_counter = lua_load_global_plugins(NULL, true);

    return plugins_counter;
}

void pilua_plugins_get_descriptions(pilua_plugin_description_callback callback, void *user_data)
{
    pilua_plugin  *lua_plug;

    for (lua_plug = pilua_plugin_list; lua_plug != NULL; lua_plug = lua_plug->next)
    {
        callback(lua_plug->name, lua_plug->version, pilua_plugin_type_name(),
                 lua_plug->filename, user_data);
    }
}

static void print_pilua_plugin_description(const char *name, const char *version,
                               const char *description, const char *filename,
                               void *user_data)
{
    printf("%s\t%s\t%s\t%s\n", name, version, description, filename);
}

void pilua_plugins_dump_all(void)
{
    pilua_plugins_get_descriptions(print_pilua_plugin_description, NULL);
}

const char *pilua_plugin_type_name(void)
{
    return "lua script";
}

static void * pilua_allocf(void *ud, void *ptr, size_t osize, size_t nsize)
{
    return realloc(ptr, nsize);
}

#define PILUA_EPAN_ENUMS_TABLE      "_EPAN"
#define PILUA_WTAP_ENUMS_TABLE      "_WTAP"

#define PILUA_BASE_TABLE            "base"
#define PILUA_FTYPE_TABLE           "ftypes"
#define PILUA_FRAMETYPE_TABLE       "frametype"
#define PILUA_EXPERT_TABLE          "expert"
#define PILUA_EXPERT_GROUP_TABLE    "group"
#define PILUA_EXPERT_SEVERITY_TABLE "severity"

static pi_enum_t const all_enums[] = {
    ENUM(ABSOLUTE_TIME_DOY_UTC),
    ENUM(ABSOLUTE_TIME_LOCAL),
    ENUM(ABSOLUTE_TIME_NTP_UTC),
    ENUM(ABSOLUTE_TIME_UNIX),
    ENUM(ABSOLUTE_TIME_UTC),
//    ENUM(AT_AX25),
//    ENUM(AT_END_OF_LIST),
//    ENUM(AT_ETHER),
//    ENUM(AT_EUI64),
//    ENUM(AT_FC),
//    ENUM(AT_FCWWN),
//    ENUM(AT_IB),
//    ENUM(AT_IPX),
//    ENUM(AT_IPv4),
//    ENUM(AT_IPv6),
//    ENUM(AT_MCTP),
//    ENUM(AT_NONE),
//    ENUM(AT_NUMERIC),
//    ENUM(AT_STRINGZ),
//    ENUM(AT_VINES),
    ENUM(BASE_ALLOW_ZERO),
    ENUM(BASE_CUSTOM),
    ENUM(BASE_DEC),
    ENUM(BASE_DEC_HEX),
    ENUM(BASE_EXP),
    ENUM(BASE_EXT_STRING),
    ENUM(BASE_HEX),
    ENUM(BASE_HEX_DEC),
    ENUM(BASE_NETMASK),
    ENUM(BASE_NONE),
    ENUM(BASE_NO_DISPLAY_VALUE),
    ENUM(BASE_OCT),
    ENUM(BASE_OUI),
    ENUM(BASE_PROTOCOL_INFO),
    ENUM(BASE_PT_DCCP),
    ENUM(BASE_PT_SCTP),
    ENUM(BASE_PT_TCP),
    ENUM(BASE_PT_UDP),
    ENUM(BASE_RANGE_STRING),
    ENUM(BASE_SHOW_ASCII_PRINTABLE),
    ENUM(BASE_SHOW_UTF_8_PRINTABLE),
    ENUM(BASE_SPECIAL_VALS),
    ENUM(BASE_UNIT_STRING),
    ENUM(BASE_VAL64_STRING),
//    ENUM(BMT_NO_APPEND),
//    ENUM(BMT_NO_FALSE),
//    ENUM(BMT_NO_FLAGS),
//    ENUM(BMT_NO_INT),
//    ENUM(BMT_NO_TFS),
//    ENUM(ENC_3GPP_TS_23_038_7BITS),
//    ENUM(ENC_3GPP_TS_23_038_7BITS_PACKED),
//    ENUM(ENC_3GPP_TS_23_038_7BITS_UNPACKED),
//    ENUM(ENC_ANTI_HOST_ENDIAN),
//    ENUM(ENC_APN_STR),
    ENUM(ENC_ASCII),
//    ENUM(ENC_ASCII_7BITS),
//    ENUM(ENC_BCD_DIGITS_0_9),
//    ENUM(ENC_BCD_ODD_NUM_DIG),
//    ENUM(ENC_BCD_SKIP_FIRST),
    ENUM(ENC_BIG_ENDIAN),
//    ENUM(ENC_BOM),
//    ENUM(ENC_CHARENCODING_MASK),
//    ENUM(ENC_CP437),
//    ENUM(ENC_CP855),
//    ENUM(ENC_CP866),
//    ENUM(ENC_DECT_STANDARD_4BITS_TBCD),
//    ENUM(ENC_DECT_STANDARD_8BITS),
//    ENUM(ENC_EBCDIC),
//    ENUM(ENC_EBCDIC_CP037),
//    ENUM(ENC_EBCDIC_CP500),
//    ENUM(ENC_ETSI_TS_102_221_ANNEX_A),
//    ENUM(ENC_EUC_KR),
    ENUM(ENC_GB18030),
//    ENUM(ENC_HOST_ENDIAN),
//    ENUM(ENC_IMF_DATE_TIME),
//    ENUM(ENC_ISO_646_BASIC),
//    ENUM(ENC_ISO_646_IRV),
//    ENUM(ENC_ISO_8601_DATE),
//    ENUM(ENC_ISO_8601_DATE_TIME),
//    ENUM(ENC_ISO_8601_DATE_TIME_BASIC),
//    ENUM(ENC_ISO_8601_TIME),
//    ENUM(ENC_ISO_8859_1),
//    ENUM(ENC_ISO_8859_10),
//    ENUM(ENC_ISO_8859_11),
//    ENUM(ENC_ISO_8859_13),
//    ENUM(ENC_ISO_8859_14),
//    ENUM(ENC_ISO_8859_15),
//    ENUM(ENC_ISO_8859_16),
//    ENUM(ENC_ISO_8859_2),
//    ENUM(ENC_ISO_8859_3),
//    ENUM(ENC_ISO_8859_4),
//    ENUM(ENC_ISO_8859_5),
//    ENUM(ENC_ISO_8859_6),
//    ENUM(ENC_ISO_8859_7),
//    ENUM(ENC_ISO_8859_8),
//    ENUM(ENC_ISO_8859_9),
//    ENUM(ENC_KEYPAD_ABC_TBCD),
//    ENUM(ENC_KEYPAD_BC_TBCD),
//    ENUM(ENC_LITTLE_ENDIAN),
//    ENUM(ENC_MAC_ROMAN),
//    ENUM(ENC_NA),
//    ENUM(ENC_NUM_PREF),
//    ENUM(ENC_RFC_1123),
//    ENUM(ENC_RFC_822),
    ENUM(ENC_SEP_COLON),
    ENUM(ENC_SEP_DASH),
    ENUM(ENC_SEP_DOT),
    ENUM(ENC_SEP_MASK),
    ENUM(ENC_SEP_NONE),
    ENUM(ENC_SEP_SPACE),
    ENUM(ENC_STRING),
    ENUM(ENC_STR_HEX),
    ENUM(ENC_STR_MASK),
    ENUM(ENC_STR_NUM),
    ENUM(ENC_STR_TIME_MASK),
//    ENUM(ENC_T61),
//    ENUM(ENC_TIME_CLASSIC_MAC_OS_SECS),
//    ENUM(ENC_TIME_MIP6),
//    ENUM(ENC_TIME_MP4_FILE_SECS),
//    ENUM(ENC_TIME_MSECS),
//    ENUM(ENC_TIME_MSEC_NTP),
//    ENUM(ENC_TIME_NSECS),
//    ENUM(ENC_TIME_NTP),
//    ENUM(ENC_TIME_NTP_BASE_ZERO),
//    ENUM(ENC_TIME_RFC_3971),
//    ENUM(ENC_TIME_RTPS),
//    ENUM(ENC_TIME_SECS),
//    ENUM(ENC_TIME_SECS_NSECS),
//    ENUM(ENC_TIME_SECS_NTP),
//    ENUM(ENC_TIME_SECS_USECS),
//    ENUM(ENC_TIME_TIMESPEC),
//    ENUM(ENC_TIME_TIMEVAL),
//    ENUM(ENC_TIME_TOD),
//    ENUM(ENC_TIME_USECS),
//    ENUM(ENC_TIME_ZBEE_ZCL),
//    ENUM(ENC_UCS_2),
//    ENUM(ENC_UCS_4),
//    ENUM(ENC_UTF_16),
    ENUM(ENC_UTF_8),
//    ENUM(ENC_VARINT_MASK),
//    ENUM(ENC_VARINT_PROTOBUF),
//    ENUM(ENC_VARINT_QUIC),
//    ENUM(ENC_VARINT_SDNV),
//    ENUM(ENC_VARINT_ZIGZAG),
//    ENUM(ENC_WINDOPI_1250),
//    ENUM(ENC_WINDOPI_1251),
//    ENUM(ENC_WINDOPI_1252),
//    ENUM(ENC_ZIGBEE),
//    ENUM(FIELD_DISPLAY_E_MASK),
    ENUM(FI_BIG_ENDIAN),
    ENUM(FI_GENERATED),
    ENUM(FI_HIDDEN),
    ENUM(FI_LITTLE_ENDIAN),
    ENUM(FI_URL),
    ENUM(FI_VARINT),
    ENUM(FTREPR_DFILTER),
    ENUM(FTREPR_DISPLAY),
    ENUM(FTREPR_JSON),
    ENUM(FT_ABSOLUTE_TIME),
    ENUM(FT_AX25),
    ENUM(FT_AX25_ADDR_LEN),
    ENUM(FT_BADARG),
    ENUM(FT_BOOLEAN),
    ENUM(FT_BYTES),
    ENUM(FT_CHAR),
    ENUM(FT_DOUBLE),
    ENUM(FT_ERROR),
    ENUM(FT_ETHER),
    ENUM(FT_ETHER_LEN),
    ENUM(FT_EUI64),
    ENUM(FT_EUI64_LEN),
    ENUM(FT_FALSE),
    ENUM(FT_FCWWN),
    ENUM(FT_FCWWN_LEN),
    ENUM(FT_FLOAT),
    ENUM(FT_FRAMENUM),
    ENUM(FT_FRAMENUM_ACK),
    ENUM(FT_FRAMENUM_DUP_ACK),
    ENUM(FT_FRAMENUM_NONE),
    ENUM(FT_FRAMENUM_NUM_TYPES),
    ENUM(FT_FRAMENUM_REQUEST),
    ENUM(FT_FRAMENUM_RESPONSE),
    ENUM(FT_FRAMENUM_RETRANS_NEXT),
    ENUM(FT_FRAMENUM_RETRANS_PREV),
    ENUM(FT_GUID),
    ENUM(FT_GUID_LEN),
    ENUM(FT_IEEE_11073_FLOAT),
    ENUM(FT_IEEE_11073_SFLOAT),
    ENUM(FT_INT16),
    ENUM(FT_INT24),
    ENUM(FT_INT32),
    ENUM(FT_INT40),
    ENUM(FT_INT48),
    ENUM(FT_INT56),
    ENUM(FT_INT64),
    ENUM(FT_INT8),
    ENUM(FT_IPXNET),
    ENUM(FT_IPXNET_LEN),
    ENUM(FT_IPv4),
    ENUM(FT_IPv4_LEN),
    ENUM(FT_IPv6),
    ENUM(FT_IPv6_LEN),
    ENUM(FT_NONE),
    ENUM(FT_NUM_TYPES),
    ENUM(FT_OID),
    ENUM(FT_OK),
    ENUM(FT_OVERFLOW),
    ENUM(FT_PROTOCOL),
    ENUM(FT_RELATIVE_TIME),
    ENUM(FT_REL_OID),
    ENUM(FT_SCALAR),
    ENUM(FT_STRING),
    ENUM(FT_STRINGZ),
    ENUM(FT_STRINGZPAD),
    ENUM(FT_STRINGZTRUNC),
    ENUM(FT_SYSTEM_ID),
    ENUM(FT_TRUE),
    ENUM(FT_UINT16),
    ENUM(FT_UINT24),
    ENUM(FT_UINT32),
    ENUM(FT_UINT40),
    ENUM(FT_UINT48),
    ENUM(FT_UINT56),
    ENUM(FT_UINT64),
    ENUM(FT_UINT8),
    ENUM(FT_UINT_BYTES),
    ENUM(FT_UINT_STRING),
    ENUM(FT_VARINT_MAX_LEN),
    ENUM(FT_VINES),
    ENUM(FT_VINES_ADDR_LEN),
//    ENUM(HF_REF_TYPE_DIRECT),
//    ENUM(HF_REF_TYPE_INDIRECT),
//    ENUM(HF_REF_TYPE_NONE),
//    ENUM(HF_REF_TYPE_PRINT),
//    ENUM(IP_PROTO_3PC),
//    ENUM(IP_PROTO_AH),
//    ENUM(IP_PROTO_AN),
//    ENUM(IP_PROTO_ARGUS),
//    ENUM(IP_PROTO_ARIS),
//    ENUM(IP_PROTO_AX25),
//    ENUM(IP_PROTO_AX4000),
//    ENUM(IP_PROTO_BBN_RCC),
//    ENUM(IP_PROTO_BNA),
//    ENUM(IP_PROTO_BRSATMON),
//    ENUM(IP_PROTO_BULK),
//    ENUM(IP_PROTO_CBT),
//    ENUM(IP_PROTO_CHAOS),
//    ENUM(IP_PROTO_CMTP),
//    ENUM(IP_PROTO_COMPAQ),
//    ENUM(IP_PROTO_CPHB),
//    ENUM(IP_PROTO_CPNX),
//    ENUM(IP_PROTO_CRTP),
//    ENUM(IP_PROTO_CRUDP),
//    ENUM(IP_PROTO_DCCP),
//    ENUM(IP_PROTO_DCNMEAS),
//    ENUM(IP_PROTO_DDP),
//    ENUM(IP_PROTO_DDX),
//    ENUM(IP_PROTO_DGP),
//    ENUM(IP_PROTO_DSR),
//    ENUM(IP_PROTO_DSTOPTS),
//    ENUM(IP_PROTO_EGP),
//    ENUM(IP_PROTO_EIGRP),
//    ENUM(IP_PROTO_EMCON),
//    ENUM(IP_PROTO_ENCAP),
//    ENUM(IP_PROTO_ESP),
//    ENUM(IP_PROTO_ETHERIP),
//    ENUM(IP_PROTO_ETHERNET),
//    ENUM(IP_PROTO_FC),
//    ENUM(IP_PROTO_FIRE),
//    ENUM(IP_PROTO_FRAGMENT),
//    ENUM(IP_PROTO_GGP),
//    ENUM(IP_PROTO_GMTP),
//    ENUM(IP_PROTO_GRE),
//    ENUM(IP_PROTO_HIP),
//    ENUM(IP_PROTO_HMP),
//    ENUM(IP_PROTO_HOPOPTS),
//    ENUM(IP_PROTO_IATP),
//    ENUM(IP_PROTO_ICMP),
//    ENUM(IP_PROTO_ICMPV6),
//    ENUM(IP_PROTO_IDP),
//    ENUM(IP_PROTO_IDPR),
//    ENUM(IP_PROTO_IDRP),
//    ENUM(IP_PROTO_IFMP),
//    ENUM(IP_PROTO_IGMP),
//    ENUM(IP_PROTO_IGP),
//    ENUM(IP_PROTO_IGRP),
//    ENUM(IP_PROTO_IL),
//    ENUM(IP_PROTO_INSLP),
//    ENUM(IP_PROTO_IPCOMP),
//    ENUM(IP_PROTO_IPCV),
//    ENUM(IP_PROTO_IPINIP),
//    ENUM(IP_PROTO_IPIP),
//    ENUM(IP_PROTO_IPLT),
//    ENUM(IP_PROTO_IPPC),
//    ENUM(IP_PROTO_IPV4),
//    ENUM(IP_PROTO_IPV6),
//    ENUM(IP_PROTO_IPX),
//    ENUM(IP_PROTO_IRT),
//    ENUM(IP_PROTO_ISIS),
//    ENUM(IP_PROTO_ISOIP),
//    ENUM(IP_PROTO_KRYPTOLAN),
//    ENUM(IP_PROTO_L2TP),
//    ENUM(IP_PROTO_LARP),
//    ENUM(IP_PROTO_LEAF1),
//    ENUM(IP_PROTO_LEAF2),
//    ENUM(IP_PROTO_MANET),
//    ENUM(IP_PROTO_MERIT),
//    ENUM(IP_PROTO_MFE_NSP),
//    ENUM(IP_PROTO_MICP),
//    ENUM(IP_PROTO_MIPV6),
//    ENUM(IP_PROTO_MIPV6_OLD),
//    ENUM(IP_PROTO_MOBILE),
//    ENUM(IP_PROTO_MPLS_IN_IP),
//    ENUM(IP_PROTO_MTP),
//    ENUM(IP_PROTO_MUX),
//    ENUM(IP_PROTO_NARP),
//    ENUM(IP_PROTO_NCS_HEARTBEAT),
//    ENUM(IP_PROTO_NONE),
//    ENUM(IP_PROTO_NSFNETIGP),
//    ENUM(IP_PROTO_NVPII),
//    ENUM(IP_PROTO_OSPF),
//    ENUM(IP_PROTO_PGM),
//    ENUM(IP_PROTO_PIM),
//    ENUM(IP_PROTO_PIPE),
//    ENUM(IP_PROTO_PNNI),
//    ENUM(IP_PROTO_PRM),
//    ENUM(IP_PROTO_PTP),
//    ENUM(IP_PROTO_PUP),
//    ENUM(IP_PROTO_PVP),
//    ENUM(IP_PROTO_QNX),
//    ENUM(IP_PROTO_RDP),
//    ENUM(IP_PROTO_ROHC),
//    ENUM(IP_PROTO_ROUTING),
//    ENUM(IP_PROTO_RSVP),
//    ENUM(IP_PROTO_RSVPE2EI),
//    ENUM(IP_PROTO_RVD),
//    ENUM(IP_PROTO_SATEXPAK),
//    ENUM(IP_PROTO_SATMON),
//    ENUM(IP_PROTO_SCCCP),
//    ENUM(IP_PROTO_SCPS),
//    ENUM(IP_PROTO_SCTP),
//    ENUM(IP_PROTO_SDRP),
//    ENUM(IP_PROTO_SHIM6),
//    ENUM(IP_PROTO_SKIP),
//    ENUM(IP_PROTO_SM),
//    ENUM(IP_PROTO_SMP),
//    ENUM(IP_PROTO_SNP),
//    ENUM(IP_PROTO_SPRITE),
//    ENUM(IP_PROTO_SPS),
//    ENUM(IP_PROTO_SRP),
//    ENUM(IP_PROTO_SSCOPMCE),
//    ENUM(IP_PROTO_STP),
//    ENUM(IP_PROTO_STREAM),
//    ENUM(IP_PROTO_SUNND),
//    ENUM(IP_PROTO_SVMTP),
//    ENUM(IP_PROTO_SWIPE),
//    ENUM(IP_PROTO_TCF),
//    ENUM(IP_PROTO_TCP),
//    ENUM(IP_PROTO_TLSP),
//    ENUM(IP_PROTO_TP),
//    ENUM(IP_PROTO_TPPP),
//    ENUM(IP_PROTO_TRUNK1),
//    ENUM(IP_PROTO_TRUNK2),
//    ENUM(IP_PROTO_TTP),
//    ENUM(IP_PROTO_UDP),
//    ENUM(IP_PROTO_UDPLITE),
//    ENUM(IP_PROTO_UTI),
//    ENUM(IP_PROTO_VINES),
//    ENUM(IP_PROTO_VISA),
//    ENUM(IP_PROTO_VMTP),
//    ENUM(IP_PROTO_VRRP),
//    ENUM(IP_PROTO_WBEXPAK),
//    ENUM(IP_PROTO_WBMON),
//    ENUM(IP_PROTO_WESP),
//    ENUM(IP_PROTO_PIN),
//    ENUM(IP_PROTO_XNET),
//    ENUM(IP_PROTO_XTP),
//    ENUM(ITEM_LABEL_LENGTH),
//    ENUM(PI_ASSUMPTION),
//    ENUM(PI_CHAT),
//    ENUM(PI_CHECKSUM),
//    ENUM(PI_COMMENT),
//    ENUM(PI_COMMENTS_GROUP),
//    ENUM(PI_DEBUG),
//    ENUM(PI_DECRYPTION),
//    ENUM(PI_DEPRECATED),
//    ENUM(PI_DISSECTOR_BUG),
//    ENUM(PI_ERROR),
//    ENUM(PI_GROUP_MASK),
//    ENUM(PI_INTERFACE),
//    ENUM(PI_MALFORMED),
//    ENUM(PI_NOTE),
//    ENUM(PI_PROTOCOL),
//    ENUM(PI_REASSEMBLE),
//    ENUM(PI_RECEIVE),
//    ENUM(PI_REQUEST_CODE),
//    ENUM(PI_RESPONSE_CODE),
//    ENUM(PI_SECURITY),
//    ENUM(PI_SEQUENCE),
//    ENUM(PI_SEVERITY_MASK),
//    ENUM(PI_UNDECODED),
//    ENUM(PI_WARN),
//    ENUM(PROTO_CHECKSUM_E_BAD),
//    ENUM(PROTO_CHECKSUM_E_GOOD),
//    ENUM(PROTO_CHECKSUM_E_ILLEGAL),
//    ENUM(PROTO_CHECKSUM_E_NOT_PRESENT),
//    ENUM(PROTO_CHECKSUM_E_UNVERIFIED),
//    ENUM(PROTO_CHECKSUM_GENERATED),
//    ENUM(PROTO_CHECKSUM_IN_CKSUM),
//    ENUM(PROTO_CHECKSUM_NOT_PRESENT),
//    ENUM(PROTO_CHECKSUM_NO_FLAGS),
//    ENUM(PROTO_CHECKSUM_VERIFY),
//    ENUM(PROTO_CHECKSUM_ZERO),
//    ENUM(PT_BLUETOOTH),
//    ENUM(PT_DCCP),
//    ENUM(PT_DDP),
//    ENUM(PT_I2C),
//    ENUM(PT_IBQP),
//    ENUM(PT_IDP),
//    ENUM(PT_IPX),
//    ENUM(PT_IWARP_MPA),
//    ENUM(PT_MCTP),
//    ENUM(PT_NONE),
//    ENUM(PT_SCTP),
//    ENUM(PT_TCP),
//    ENUM(PT_UDP),
//    ENUM(PT_USB),
//    ENUM(REGISTER_ANALYZE_GROUP_CONVERSATION_FILTER),
//    ENUM(REGISTER_LOG_ANALYZE_GROUP_UNSORTED),
//    ENUM(REGISTER_LOG_STAT_GROUP_UNSORTED),
//    ENUM(REGISTER_PACKET_ANALYZE_GROUP_UNSORTED),
//    ENUM(REGISTER_PACKET_STAT_GROUP_UNSORTED),
//    ENUM(REGISTER_STAT_GROUP_CONVERSATION_LIST),
//    ENUM(REGISTER_STAT_GROUP_ENDPOINT_LIST),
//    ENUM(REGISTER_STAT_GROUP_GENERIC),
//    ENUM(REGISTER_STAT_GROUP_RESPONSE_TIME),
//    ENUM(REGISTER_STAT_GROUP_RSERPOOL),
//    ENUM(REGISTER_TELEPHONY_GROUP_3GPP_UU),
//    ENUM(REGISTER_TELEPHONY_GROUP_ANSI),
//    ENUM(REGISTER_TELEPHONY_GROUP_GSM),
//    ENUM(REGISTER_TELEPHONY_GROUP_MTP3),
//    ENUM(REGISTER_TELEPHONY_GROUP_SCTP),
//    ENUM(REGISTER_TELEPHONY_GROUP_UNSORTED),
//    ENUM(REGISTER_TOOLS_GROUP_UNSORTED),
    ENUM(SEP_COLON),
    ENUM(SEP_DASH),
    ENUM(SEP_DOT),
    ENUM(SEP_SPACE),
//    ENUM(ST_FORMAT_CSV),
//    ENUM(ST_FORMAT_PLAIN),
//    ENUM(ST_FORMAT_XML),
//    ENUM(ST_FORMAT_YAML),
    { NULL, 0 },
};

static void add_table_symbol(const char *table, const char *name, int value)
{
    /* Get table from the global environment. */
    lua_getglobal(L, table);
    /* Set symbol in table. */
    lua_pushstring(L, name);
    lua_pushinteger(L, value);
    lua_settable(L, -3);
    /* Pop table from stack. */
    lua_pop(L, 1);
}

static void add_global_symbol(const char *name, int value)
{
    /* Set symbol in global environment. */
    lua_pushinteger(L, value);
    lua_setglobal(L, name);
}

static void add_pi_severity_symbol(const char *name, int value)
{
    lua_getglobal(L, PILUA_EXPERT_TABLE);
    lua_getfield(L, -1, PILUA_EXPERT_SEVERITY_TABLE);
    lua_pushinteger(L, value);
    lua_setfield(L, -2, name);
    lua_pop(L, 2);
}

static void add_pi_group_symbol(const char *name, int value)
{
    lua_getglobal(L, PILUA_EXPERT_TABLE);
    lua_getfield(L, -1, PILUA_EXPERT_GROUP_TABLE);
    lua_pushinteger(L, value);
    lua_setfield(L, -2, name);
    lua_pop(L, 2);
}

/*
 * Read introspection constants and add them according to the historical
 * (sometimes arbitrary) rules of make-init-lua.py. For efficiency reasons
 * we only loop the enums array once.
 */
static void pilua_add_introspection(void)
{
    const pi_enum_t *ep;

    /* Add empty tables to be populated. */
    lua_newtable(L);
    lua_setglobal(L, PILUA_BASE_TABLE);
    lua_newtable(L);
    lua_setglobal(L, PILUA_FTYPE_TABLE);
    lua_newtable(L);
    lua_setglobal(L, PILUA_FRAMETYPE_TABLE);
    lua_newtable(L);
    lua_pushstring(L, PILUA_EXPERT_GROUP_TABLE);
    lua_newtable(L);
    lua_settable(L, -3);
    lua_pushstring(L, PILUA_EXPERT_SEVERITY_TABLE);
    lua_newtable(L);
    lua_settable(L, -3);
    lua_setglobal(L, PILUA_EXPERT_TABLE);
    /* Add catch-all _EPAN table. */
    lua_newtable(L);
    lua_setglobal(L, PILUA_EPAN_ENUMS_TABLE);

    for (ep = all_enums; ep->symbol != NULL; ep++) {

        if (g_str_has_prefix(ep->symbol, "BASE_")) {
            add_table_symbol(PILUA_BASE_TABLE, ep->symbol + strlen("BASE_"), ep->value);
        }
        else if (g_str_has_prefix(ep->symbol, "SEP_")) {
            add_table_symbol(PILUA_BASE_TABLE, ep->symbol + strlen("SEP_"), ep->value);
        }
        else if (g_str_has_prefix(ep->symbol, "ABSOLUTE_TIME_")) {
            add_table_symbol(PILUA_BASE_TABLE, ep->symbol + strlen("ABSOLUTE_TIME_"), ep->value);
        }
        else if (g_str_has_prefix(ep->symbol, "ENC_")) {
            add_global_symbol(ep->symbol, ep->value);
        }
        else if (g_str_has_prefix(ep->symbol, "FT_FRAMENUM_")) {
            add_table_symbol(PILUA_FRAMETYPE_TABLE, ep->symbol + strlen("FT_FRAMENUM_"), ep->value);
        }
        else if (g_str_has_prefix(ep->symbol, "FT_")) {
            add_table_symbol(PILUA_FTYPE_TABLE, ep->symbol + strlen("FT_"), ep->value);
        }
        else if (g_str_has_prefix(ep->symbol, "PI_")) {
            if (ep->value & PI_SEVERITY_MASK) {
                add_pi_severity_symbol(ep->symbol + strlen("PI_"), ep->value);
            }
            else {
                 add_pi_group_symbol(ep->symbol + strlen("PI_"), ep->value);
            }
            /* For backward compatibility. */
            add_global_symbol(ep->symbol, ep->value);
        }
//        else if (g_str_has_prefix(ep->symbol, "REGISTER_")) {
//            add_menu_group_symbol(ep->symbol + strlen("REGISTER_"), ep->value);
//        }
        add_table_symbol(PILUA_EPAN_ENUMS_TABLE, ep->symbol, ep->value);
    }
}

static int pilua_console_print(lua_State *_L);

static const char *lua_error_msg(int code)
{
    switch (code) {
        case LUA_ERRSYNTAX: return "syntax error during precompilation";
        case LUA_ERRMEM:    return "memory allocation error";
#if LUA_VERSION_NUM == 502
        case LUA_ERRGCMM:   return "error while running a __gc metamethod";
#endif
        case LUA_ERRRUN:    return "runtime error";
        case LUA_ERRERR:    return "error while running the message handler";
        default:            break; /* Should not happen. */
    }
    return "unknown error";
}

static int lua_funnel_console_eval(const char *console_input,
                                        char **error_ptr,
                                        char **error_hint,
                                        void *callback_data)
{
    int lcode;

    const int curr_top = lua_gettop(L);

    // If it starts with an equals sign replace it with "return"
    char *codestr;
    while (g_ascii_isspace(*console_input))
        console_input++;
    if (*console_input == '=')
        codestr = ws_strdup_printf("return %s", console_input+1);
    else
        codestr = (char *)console_input; /* Violate const safety to avoid a strdup() */

//    ws_noisy("Console input: %s", codestr);
    lcode = luaL_loadstring(L, codestr);
    /* Free only if we called strdup(). */
    if (codestr != console_input)
        g_free(codestr);
    codestr = NULL;

    if (lcode != LUA_OK) {
//        ws_debug("luaL_loadstring(): %s (%d)", lua_error_msg(lcode), lcode);
        if (error_hint) {
            *error_hint = g_strdup(lua_error_msg(lcode));
        }
        /* If we have an error message return it. */
        if (error_ptr && !lua_isnil(L, -1)) {
            *error_ptr = g_strdup(lua_tostring(L, -1));
        }
        return -1;
    }

    lcode = lua_pcall(L, 0, LUA_MULTRET, 0);
    if (lcode != LUA_OK) {
//        ws_debug("lua_pcall(): %s (%d)", lua_error_msg(lcode), lcode);
        if (error_hint) {
            *error_hint = g_strdup(lua_error_msg(lcode));
        }
        /* If we have an error message return it. */
        if (error_ptr && !lua_isnil(L, -1)) {
            *error_ptr = g_strdup(lua_tostring(L, -1));
        }
        return 1;
    }

    // If we have values returned print them all
    if (lua_gettop(L) > curr_top) {  /* any arguments? */
        lua_pushcfunction(L, pilua_console_print);
        lua_insert(L, curr_top+1);
        lcode = lua_pcall(L, lua_gettop(L)-curr_top-1, 0, 0);
        if (lcode != LUA_OK) {
            /* Error printing result */
            if (error_hint)
                *error_hint = ws_strdup_printf("error printing return values: %s", lua_error_msg(lcode));
            return 1;
        }
    }

    // For any new Protos, register their ProtoFields and ProtoExperts with epan
    lua_pushcfunction(L, Proto_commit);
    lcode = lua_pcall(L, 0, 0, 0);
    if (lcode != LUA_OK) {
        /* Error initializing new ProtoFields */
        if (error_hint)
            *error_hint = ws_strdup_printf("error initialzing protocol fields: %s", lua_error_msg(lcode));
        /* If we have an error message return it. */
        if (error_ptr && !lua_isnil(L, -1)) {
            *error_ptr = g_strdup(lua_tostring(L, -1));
        }
        return 1;
    }

    // Maintain stack discipline
    if (lua_gettop(L) != curr_top) {
//        ws_critical("Expected stack top == %d, have %d", curr_top, lua_gettop(L));
    }

//    ws_noisy("Success");
    return 0;
}

/* Receives C print function pointer as first upvalue. */
/* Receives C print function data pointer as second upvalue. */
static int pilua_console_print(lua_State *_L)
{
    GString *gstr = g_string_new(NULL);
    const char *repr;

    /* Print arguments. */
    for (int i = 1; i <= lua_gettop(_L); i++) {
            repr = luaL_tolstring(_L, i, NULL);
            if (i > 1)
                g_string_append_c(gstr, '\t');
            g_string_append(gstr, repr);
            lua_pop(_L, 1);
    }
    g_string_append_c(gstr, '\n');

    if (pilua_gui_print_func_ptr == NULL) {
//        ws_critical("GUI print function not registered; Trying to print: %s", gstr->str);
    } else {
        pilua_gui_print_func_ptr(gstr->str, pilua_gui_print_data_ptr);
    }
    g_string_free(gstr, TRUE);
    return 0;
}

// Replace lua print function with a custom print function.
// We will place the original function in the Lua registry and return the reference.
static void lua_funnel_console_open(void (*print_func_ptr)(const char *, void *),
                                        void *print_data_ptr,
                                        void *callback_data)
{
    /* Store original print value in the registry (even if it is nil). */
    lua_getglobal(L, "print");
    pilua_lua_print_func_ref = luaL_ref(L, LUA_REGISTRYINDEX);

    /* Set new "print" function (to output to the GUI) */
    lua_pushcfunction(L, pilua_console_print);
    lua_setglobal(L, "print");

    /* Save the globals */
    pilua_gui_print_func_ptr = print_func_ptr;
    pilua_gui_print_data_ptr = print_data_ptr;
}

// Restore original Lua print function. Clean state.
static void lua_funnel_console_close(void *callback_data)
{
    /* Restore the original print function. */
    int ref = (int)pilua_lua_print_func_ref;
    /* push original function into stack */
    lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
    lua_setglobal(L, "print");
    /* Release reference */
    luaL_unref(L, LUA_REGISTRYINDEX, ref);

    /* Clear the globals. */
    pilua_gui_print_func_ptr = NULL;
    pilua_gui_print_data_ptr = NULL;
    pilua_lua_print_func_ref = LUA_NOREF;
}

static int pilua_file_exists(lua_State *_L)
{
    const char *path = luaL_checkstring(_L, 1);
    lua_pushboolean(_L, g_file_test(path, G_FILE_TEST_EXISTS));
    return 1;
}

static int pilua_lua_typeof(lua_State *_L)
{
    const char *classname = pilua_typeof(_L, 1);
    lua_pushstring(_L, classname);
    return 1;
}

/* Other useful constants */
void pilua_add_useful_constants(void)
{
    char *path;

    /* DATA_DIR has a trailing directory separator. */
//    path = get_datafile_path("");
//    lua_pushfstring(L, "%s"DIR_SEPARATOR, path);
//    free(path);
//    lua_setglobal(L, "DATA_DIR");

    /* USER_DIR has a trailing directory separator. */
//    path = get_persconffile_path("", false);
//    lua_pushfstring(L, "%s" DIR_SEPARATOR, path);
//    free(path);
//    lua_setglobal(L, "USER_DIR");

    lua_pushcfunction(L, pilua_file_exists);
    lua_setglobal(L, "file_exists");

    lua_pushcfunction(L, pilua_lua_typeof);
    lua_setglobal(L, "typeof");

    lua_pushboolean(L, 1);
    lua_setglobal(L, "NotWireshark");
}

void pilua_init(void *client_data)
{printf("pilua_init\n");
    char* filename;
    bool enable_lua = true;
    bool run_anyway = false;
    int file_count = 1;
    static bool first_time = true;
    int i;
    int error;

    static hf_register_info hf[] = {
        { &hf_pilua_fake,
          { "Lua fake item",     "_pi.lua.fake",
            FT_NONE, BASE_NONE, NULL, 0x0,
            "Fake internal item for Lua", HFILL }},
        { &hf_pilua_text,
          { "Lua text",     "_pi.lua.text",
            FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL }},
    };
    static int *ett[] = {
            &ett_pilua_traceback,
    };

    if (!L) {
        L = lua_newstate(pilua_allocf, NULL);
    }

    PILUA_INIT(L);

    if (first_time) {
        proto_lua = proto_register_protocol("Lua Dissection", "Lua Dissection", "_pi.lua");
        proto_register_field_array(proto_lua, hf, array_length(hf));
        proto_register_subtree_array(ett, array_length(ett));
    }

    lua_atpanic(L, pilua_panic);

    /*
     * The init_routines table (accessible by the user).
     *
     * For a table a, a.init is syntactic sugar for a["init"], and
     *
     *    function t.a.b.c.f () body end
     *
     * is syntactic sugar for
     *
     *    t.a.b.c.f = function () body end
     *
     * so
     *
     *    function proto.init () body end
     *
     * means
     *
     *    proto["init"] = function () body end
     *
     * and the Proto class has an "init" method, with Proto_set_init()
     * being the setter for that method; that routine adds the Lua
     * function passed to it as a Lua argument to the PILUA_INIT_ROUTINES
     * table - i.e., "init_routines".
     */
//    lua_newtable (L);
//    lua_setglobal(L, PILUA_INIT_ROUTINES);

    /* the dissectors table goes in the registry (not accessible) */
    lua_newtable (L);
    lua_dissectors_table_ref = luaL_ref(L, LUA_REGISTRYINDEX);

    /* the preferences apply_cb table (accessible by the user) */
//    lua_newtable (L);
//    lua_setglobal(L, PILUA_PREFS_CHANGED);

    pilua_add_introspection();

    pilua_add_useful_constants();

    // Register Lua's console menu (in the GUI)
    if (first_time) {
        funnel_register_console_menu("Lua",
                                        lua_funnel_console_eval,
                                        lua_funnel_console_open,
                                        lua_funnel_console_close,
                                        NULL, NULL);
    }
    else if (pilua_gui_print_func_ptr) {
        // If we we have an open GUI console dialog re-register the global "print to console" function
        lua_funnel_console_open(pilua_gui_print_func_ptr, pilua_gui_print_data_ptr, NULL);
    }

    /* Add the global plugins path for require */
    prepend_path(get_plugins_dir());
    filename = malloc(PATH_MAX);
    snprintf(filename, PATH_MAX, "%s/init.lua", get_plugins_dir());
    if (file_exists(filename)) {
        lua_load_internal_script(filename);
    }
    free(filename);
    filename = NULL;

    /* check if lua is to be disabled */
    lua_getglobal(L, "enable_lua"); // 3.0 and later
    if (lua_isboolean(L, -1) || lua_isnil(L, -1)) {
        enable_lua = lua_toboolean(L, -1);
    }
    lua_pop(L, 1);  /* pop the getglobal result */

    if (!enable_lua) {
        /* disable lua */
        lua_close(L);
        L = NULL;
        first_time = false;
        return;
    }

    /* load global scripts */
    lua_load_global_plugins(client_data, false);

    if (first_time) {
        /* at this point we're set up so register the init and cleanup routines */
//        register_init_routine(pilua_init_routine);
//        register_cleanup_routine(pilua_cleanup_routine);
    }

    /*
     * after this point it is too late to register a menu
     * disable the function to avoid weirdness
     */
//    lua_pushcfunction(L, pilua_not_register_menu);
//    lua_setglobal(L, "register_menu");

    /* set up some essential globals */
//    lua_pinfo = NULL;
    lua_tree = NULL;
    lua_tvb = NULL;

    /* Unfortunately, by waiting to register the hfi and ei now, Lua
     * can't figure out which file had the error and provide a traceback,
     * so no special error handler.
     */
    lua_pushcfunction(L, Proto_commit);
    error = lua_pcall(L, 0, 0, 0);
    if (error) {
        switch (error) {
            case LUA_ERRRUN:
                printf("Lua: Error initializing protocols:\n%s", lua_tostring(L, -1));
                break;
            case LUA_ERRMEM:
                printf("Lua: Error initializing protocols: out of memory");
                break;
            case LUA_ERRERR:
                printf("Lua: Error initializing protocols: error while retrieving error message");
                break;
            default:
                printf("Lua: Error initializing protocols: unknown error %d", error);
                break;
        }
    }

    first_time = false;
printf("pilua_init end\n");fflush(stdout);
}

void pilua_early_cleanup(void)
{
    pilua_deregister_protocols(L);
}

void pilua_reload_plugins(void *client_data)
{
    if (L == NULL) {    // enable_lua == false or other error
        pilua_init(client_data);    /* reinitialize */
        return;
    }
//    const funnel_ops_t* ops = funnel_get_funnel_ops();

//    if (ops->close_dialogs)
//        ops->close_dialogs();

    pilua_deregister_protocols(L);
//    pilua_deregister_dissector_tables(L);
//    pilua_deregister_listeners(L);
//    pilua_deregister_fields(L);
//    pilua_deregister_filehandlers(L);

    pilua_clear_plugin_list();

    pilua_cleanup();
    pilua_init(client_data);    /* reinitialize */
}

void pilua_cleanup(void)
{
    /* cleanup lua */
    if (L) {
        lua_close(L);
        L = NULL;
    }
//    init_routine_initialized = false;
}

lua_State* pilua_state(void) { return L; }
